/*
 * Copyright 2012-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.data.redis;

import java.time.Duration;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Redis 的配置属性。
 *
 * @author Dave Syer
 * @author Christoph Strobl
 * @author Eddú Meléndez
 * @author Marco Aust
 * @author Mark Paluch
 * @author Stephane Nicoll
 * @since 1.0.0
 */
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

	/**
	 * 连接工厂使用的数据库索引。
	 */
	private int database = 0;

	/**
	 * 连接 URL。会覆盖 host、port 和 password。user 被忽略。例如：
	 * redis://user:password@example.com:6379
	 */
	private String url;

	/**
	 * Redis 服务器主机。
	 */
	private String host = "localhost";

	/**
	 * Redis 服务器的登录用户名。
	 */
	private String username;

	/**
	 * Redis 服务器的登录密码。
	 */
	private String password;

	/**
	 * Redis 服务器端口。
	 */
	private int port = 6379;

	/**
	 * 是否启用 SSL 支持。
	 */
	private boolean ssl;

	/**
	 * 读取超时。
	 */
	private Duration timeout;

	/**
	 * 连接超时。
	 */
	private Duration connectTimeout;

	/**
	 * 使用 CLIENT SETNAME 在连接上设置的客户端名称。
	 */
	private String clientName;

	/**
	 * 要使用的客户端类型。默认情况下，根据类路径自动检测。
	 */
	private ClientType clientType;

	private Sentinel sentinel;

	private Cluster cluster;

	private final Jedis jedis = new Jedis();

	private final Lettuce lettuce = new Lettuce();

	public int getDatabase() {
		return this.database;
	}

	public void setDatabase(int database) {
		this.database = database;
	}

	public String getUrl() {
		return this.url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getHost() {
		return this.host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public String getUsername() {
		return this.username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return this.password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getPort() {
		return this.port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public boolean isSsl() {
		return this.ssl;
	}

	public void setSsl(boolean ssl) {
		this.ssl = ssl;
	}

	public void setTimeout(Duration timeout) {
		this.timeout = timeout;
	}

	public Duration getTimeout() {
		return this.timeout;
	}

	public Duration getConnectTimeout() {
		return this.connectTimeout;
	}

	public void setConnectTimeout(Duration connectTimeout) {
		this.connectTimeout = connectTimeout;
	}

	public String getClientName() {
		return this.clientName;
	}

	public void setClientName(String clientName) {
		this.clientName = clientName;
	}

	public ClientType getClientType() {
		return this.clientType;
	}

	public void setClientType(ClientType clientType) {
		this.clientType = clientType;
	}

	public Sentinel getSentinel() {
		return this.sentinel;
	}

	public void setSentinel(Sentinel sentinel) {
		this.sentinel = sentinel;
	}

	public Cluster getCluster() {
		return this.cluster;
	}

	public void setCluster(Cluster cluster) {
		this.cluster = cluster;
	}

	public Jedis getJedis() {
		return this.jedis;
	}

	public Lettuce getLettuce() {
		return this.lettuce;
	}

	/**
	 * 要使用的 Redis 客户端类型。
	 */
	public enum ClientType {

		/**
		 * 使用 Lettuce Redis 客户端。
		 */
		LETTUCE,

		/**
		 * 使用 Jedis Redis 客户端。
		 */
		JEDIS

	}

	/**
	 * 池属性。
	 */
	public static class Pool {

		/**
		 * 是否启用连接池。如果 "commons-pool2" 可用，则会自动启用。
		 * 对于 Jedis，在 sentinel 模式下会隐式启用连接池，此设置仅适用于单节点配置。
		 */
		private Boolean enabled;

		/**
		 * 池中“空闲”连接的最大数量。使用负值表示空闲连接数量不受限制。
		 */
		private int maxIdle = 8;

		/**
		 * 池中要保持的最小空闲连接数目标。此设置仅在它和驱逐运行之间的时间间隔都为正时才有效。
		 */
		private int minIdle = 0;

		/**
		 * 池在给定时间可以分配的最大连接数。使用负值表示没有限制。
		 */
		private int maxActive = 8;

		/**
		 * 当池耗尽时，连接分配在抛出异常前应阻塞的最长时间。使用负值表示无限期阻塞。
		 */
		private Duration maxWait = Duration.ofMillis(-1);

		/**
		 * 空闲对象驱逐器线程运行之间的时间。当为正时，空闲对象驱逐器线程启动，否则不执行空闲对象驱逐。
		 */
		private Duration timeBetweenEvictionRuns;

		public Boolean getEnabled() {
			return this.enabled;
		}

		public void setEnabled(Boolean enabled) {
			this.enabled = enabled;
		}

		public int getMaxIdle() {
			return this.maxIdle;
		}

		public void setMaxIdle(int maxIdle) {
			this.maxIdle = maxIdle;
		}

		public int getMinIdle() {
			return this.minIdle;
		}

		public void setMinIdle(int minIdle) {
			this.minIdle = minIdle;
		}

		public int getMaxActive() {
			return this.maxActive;
		}

		public void setMaxActive(int maxActive) {
			this.maxActive = maxActive;
		}

		public Duration getMaxWait() {
			return this.maxWait;
		}

		public void setMaxWait(Duration maxWait) {
			this.maxWait = maxWait;
		}

		public Duration getTimeBetweenEvictionRuns() {
			return this.timeBetweenEvictionRuns;
		}

		public void setTimeBetweenEvictionRuns(Duration timeBetweenEvictionRuns) {
			this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;
		}

	}

	/**
	 * 集群属性。
	 */
	public static class Cluster {

		/**
		 * 用于引导的“host:port”对的逗号分隔列表。这代表了集群节点的“初始”列表，并且要求至少有一个条目。
		 */
		private List<String> nodes;

		/**
		 * 在整个集群中执行命令时要遵循的最大重定向数。
		 */
		private Integer maxRedirects;

		public List<String> getNodes() {
			return this.nodes;
		}

		public void setNodes(List<String> nodes) {
			this.nodes = nodes;
		}

		public Integer getMaxRedirects() {
			return this.maxRedirects;
		}

		public void setMaxRedirects(Integer maxRedirects) {
			this.maxRedirects = maxRedirects;
		}

	}

	/**
	 * Redis sentinel 属性。
	 */
	public static class Sentinel {

		/**
		 * Redis 服务器的名称。
		 */
		private String master;

		/**
		 * “host:port”对的逗号分隔列表。
		 */
		private List<String> nodes;

		/**
		 * 用于与 sentinel(s) 认证的登录用户名。
		 */
		private String username;

		/**
		 * 用于与 sentinel(s) 认证的密码。
		 */
		private String password;

		public String getMaster() {
			return this.master;
		}

		public void setMaster(String master) {
			this.master = master;
		}

		public List<String> getNodes() {
			return this.nodes;
		}

		public void setNodes(List<String> nodes) {
			this.nodes = nodes;
		}

		public String getUsername() {
			return this.username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

		public String getPassword() {
			return this.password;
		}

		public void setPassword(String password) {
			this.password = password;
		}

	}

	/**
	 * Jedis 客户端属性。
	 */
	public static class Jedis {

		/**
		 * Jedis 连接池配置。
		 */
		private final Pool pool = new Pool();

		public Pool getPool() {
			return this.pool;
		}

	}

	/**
	 * Lettuce 客户端属性。
	 */
	public static class Lettuce {

		/**
		 * 关闭超时。
		 */
		private Duration shutdownTimeout = Duration.ofMillis(100);

		/**
		 * Lettuce 连接池配置。
		 */
		private final Pool pool = new Pool();

		private final Cluster cluster = new Cluster();

		public Duration getShutdownTimeout() {
			return this.shutdownTimeout;
		}

		public void setShutdownTimeout(Duration shutdownTimeout) {
			this.shutdownTimeout = shutdownTimeout;
		}

		public Pool getPool() {
			return this.pool;
		}

		public Cluster getCluster() {
			return this.cluster;
		}

		public static class Cluster {

			private final Refresh refresh = new Refresh();

			public Refresh getRefresh() {
				return this.refresh;
			}

			public static class Refresh {

				/**
				 * 是否发现并查询所有集群节点以获取集群拓扑。当设置为 false 时，仅使用初始种子节点作为拓扑发现的来源。
				 */
				private boolean dynamicRefreshSources = true;

				/**
				 * 集群拓扑刷新周期。
				 */
				private Duration period;

				/**
				 * 是否应使用包含所有可用刷新触发器的自适应拓扑刷新。
				 */
				private boolean adaptive;

				public boolean isDynamicRefreshSources() {
					return this.dynamicRefreshSources;
				}

				public void setDynamicRefreshSources(boolean dynamicRefreshSources) {
					this.dynamicRefreshSources = dynamicRefreshSources;
				}

				public Duration getPeriod() {
					return this.period;
				}

				public void setPeriod(Duration period) {
					this.period = period;
				}

				public boolean isAdaptive() {
					return this.adaptive;
				}

				public void setAdaptive(boolean adaptive) {
					this.adaptive = adaptive;
				}

			}

		}

	}

}
